package com.fuyao.cloud.admin.flow.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.fuyao.cloud.admin.api.entity.SysPost;
import com.fuyao.cloud.admin.api.entity.SysUser;
import com.fuyao.cloud.admin.flow.dto.Handler;
import com.fuyao.cloud.admin.flow.service.flowable.ActorUserService;
import com.fuyao.cloud.admin.service.SysUserService;
import com.fuyao.cloud.common.security.util.SecurityUtils;
import com.fuyao.cloud.flowable.entity.ActorUser;
import com.fuyao.cloud.flowable.vo.ActorInstance;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.Expression;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.de.odysseus.el.misc.TypeConverter;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * Flowable 流程操作助手
 */
public class FlowableHelper {

    /**
     * 获取节点入口连线
     *
     * @param flowElement 流程元素
     * @return 节点入口连线
     */
    public static Set<SequenceFlow> getIncoming(FlowElement flowElement) {
        Set<SequenceFlow> flows = new HashSet<>();
        List<SequenceFlow> sequenceFlows = flowElement instanceof FlowNode ? ((FlowNode) flowElement).getIncomingFlows() : null;
        if (sequenceFlows != null) {
            flows.addAll(sequenceFlows);
        }
        return flows;
    }

    /**
     * 获取节点可用入口连线
     *
     * @param flowElement 流程元素
     * @param variables   流程变量
     * @return 节点入口连线
     */
    public static Set<SequenceFlow> getIncoming(FlowElement flowElement, Map<String, Object> variables) {
        return filterSequenceFlow(getIncoming(flowElement), variables);
    }

    /**
     * 获取节点出口连线
     *
     * @param flowElement 流程元素
     * @return 节点出口连线
     */
    public static Set<SequenceFlow> getOutgoing(FlowElement flowElement) {
        Set<SequenceFlow> flows = new HashSet<>();
        List<SequenceFlow> sequenceFlows = flowElement instanceof FlowNode ? ((FlowNode) flowElement).getOutgoingFlows() : null;
        if (sequenceFlows != null) {
            flows.addAll(sequenceFlows);
        }
        return flows;
    }

    /**
     * 获取节点可用出口连线
     *
     * @param flowElement 流程元素
     * @param variables   流程变量
     * @return 节点出口连线
     */
    public static Set<SequenceFlow> getOutgoing(FlowElement flowElement, Map<String, Object> variables) {
        return filterSequenceFlow(getOutgoing(flowElement), variables);
    }

    /**
     * 获取符合条件的流向线
     *
     * @param sequenceFlows 流向线
     * @param variables     评估变量
     * @return 流向线
     */
    public static Set<SequenceFlow> filterSequenceFlow(Set<SequenceFlow> sequenceFlows, Map<String, Object> variables) {
        Set<SequenceFlow> available = new HashSet<>();
        if (sequenceFlows != null) {
            sequenceFlows.forEach(sequenceFlow -> {
                String expression = sequenceFlow.getConditionExpression();
                if (expression != null && expression.trim().length() > 0) {
                    if (evaluateExpression(expression, variables)) {
                        available.add(sequenceFlow);
                    }
                } else {
                    available.add(sequenceFlow);
                }
            });
        }
        return available;
    }

    /**
     * 任务实例转成流程中的任务节点
     *
     * @param repositoryService RepositoryService
     * @param task              任务实例
     * @return 任务节点
     */
    public static UserTask taskToUserTask(RepositoryService repositoryService, Task task) {
        ProcessDefinition processDefinition = repositoryService
                .createProcessDefinitionQuery()
                .processDefinitionId(task.getProcessDefinitionId())
                .singleResult();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        String taskDefinitionKey = task.getTaskDefinitionKey();
        FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
        return flowElement instanceof UserTask ? (UserTask) flowElement : null;
    }

    public static Set<FlowElement> getAllFlowElements(BpmnModel bpmnModel) {
        return getElements(bpmnModel.getMainProcess().getFlowElements());
    }

    private static Set<FlowElement> getElements(Collection<FlowElement> flowElements) {
        Set<FlowElement> elements = new HashSet<>();
        flowElements.forEach(flowElement -> {
            if (!(flowElement instanceof SubProcess)) {
                elements.add(flowElement);
            } else {
                elements.addAll(getElements(((SubProcess) flowElement).getFlowElements()));
            }
        });
        return elements;
    }

    public static Map<String, FlowElement> getAllFlowElementsMap(BpmnModel bpmnModel) {
        return getElementsMap(bpmnModel.getMainProcess().getFlowElements());
    }

    private static Map<String, FlowElement> getElementsMap(Collection<FlowElement> flowElements) {
        Map<String, FlowElement> elements = new HashMap<>();
        flowElements.forEach(flowElement -> {
            if (!(flowElement instanceof SubProcess)) {
                elements.put(flowElement.getId(), flowElement);
            } else {
                elements.putAll(getElementsMap(((SubProcess) flowElement).getFlowElements()));
            }
        });
        return elements;
    }

    public static List<UserTask> getCurrentNodes(RuntimeService runtimeService, RepositoryService repositoryService, TaskInfo taskInfo) {
        List<String> currentActivityIds = runtimeService.getActiveActivityIds(taskInfo.getProcessInstanceId());
        return getFlowElements(repositoryService.getBpmnModel(taskInfo.getProcessDefinitionId()), currentActivityIds);
    }

    public static List<HistoricTaskInstance> getCurrentNodeDoneTask(RuntimeService runtimeService, HistoryService historyService, TaskInfo taskInfo) {
        List<String> currentActivityIds = runtimeService.getActiveActivityIds(taskInfo.getProcessInstanceId());
        return historyService.createHistoricTaskInstanceQuery()
                .unfinished()
                .caseDefinitionKeyIn(currentActivityIds)
                .list();
    }

    /**
     * 获取下一步骤的用户任务
     *
     * @param repositoryService RepositoryService
     * @param variables         variables
     * @return userTasks
     */
    public static List<UserTask> findNextNodes(RepositoryService repositoryService, TaskInfo task, Map<String, Object> variables) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        String taskDefinitionKey = task.getTaskDefinitionKey();
        Map<String, FlowElement> flowElementsMap = getAllFlowElementsMap(bpmnModel);
        return findNextNodes(flowElementsMap, flowElementsMap.get(taskDefinitionKey), variables);
    }

    /**
     * 获取 startElement 接下来的任务节点
     *
     * @param flowElementsMap 流程元素 Map
     * @param startElement    参考元素
     * @param variables       流程变量
     * @return 获取到的任务节点
     */
    public static List<UserTask> findNextNodes(Map<String, FlowElement> flowElementsMap, FlowElement startElement, Map<String, Object> variables) {
        List<UserTask> userTasks = new ArrayList<>();

        if (isEndEvent(startElement)) {
            startElement = startElement.getSubProcess();
        }

        if (startElement instanceof FlowNode) {

            Set<SequenceFlow> outGoingFlows = FlowableHelper.getOutgoing(startElement, variables);

            for (SequenceFlow sequenceFlow : outGoingFlows) {
                FlowElement nextFlowElement = flowElementsMap.get(sequenceFlow.getTargetRef());

                if (isUserTask(nextFlowElement)) {
                    userTasks.add((UserTask) nextFlowElement);
                } else if (isSubProcess(nextFlowElement)) {
                    SubProcess subProcess = (SubProcess) nextFlowElement;
                    if (hasLoopCharacteristics(subProcess)) {
                        userTasks.add(subProcessToUserTask(subProcess));
                    }
                } else {
                    userTasks.addAll(findNextNodes(flowElementsMap, nextFlowElement, variables));
                }
            }
        }

        return userTasks;
    }

    /**
     * 子流程节点转用户任务节点
     *
     * @param subProcess 流程元素
     * @return 是不是
     */
    public static UserTask subProcessToUserTask(SubProcess subProcess) {
        if (subProcess.getLoopCharacteristics() != null) {
            UserTask userTask = new UserTask();
            userTask.setId(subProcess.getId());
            userTask.setLoopCharacteristics(subProcess.getLoopCharacteristics());
            userTask.setName(subProcess.getName());
            return userTask;
        }
        return null;
    }

    public static boolean hasLoopCharacteristics(SubProcess subProcess) {
        return subProcess.getLoopCharacteristics() != null;
    }

    /**
     * 判断一个节点是不是子流程
     *
     * @param flowElement 流程元素
     * @return true 是, false 不是
     */
    public static boolean isSubProcess(FlowElement flowElement) {
        return flowElement instanceof SubProcess;
    }

    public static boolean isUserTask(FlowElement flowElement) {
        return flowElement instanceof UserTask;
    }

    /**
     * 判断一个节点是不是结束事件节点
     *
     * @param flowElement 流程元素
     * @return true 是, false 不是
     */
    public static boolean isEndEvent(FlowElement flowElement) {
        return flowElement instanceof EndEvent;
    }

    /**
     * 获取流程的开始事件节点
     *
     * @param flowElements 节点集合
     */
    public static StartEvent getStartFlowElement(Collection<FlowElement> flowElements) {
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof StartEvent) {
                return (StartEvent) flowElement;
            }
        }
        return null;
    }

    public static <T extends FlowElement> List<T> getFlowElements(BpmnModel bpmnModel, Collection<String> flowElementIds) {
        return flowElementIds.stream().map(s -> (T) bpmnModel.getFlowElement(s)).collect(Collectors.toList());
    }

    public static StartEvent getStartFlowElement(RepositoryService repositoryService, String processDefinitionId) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
        return getStartFlowElement(flowElements);
    }

    /**
     * 评估表达式并获取评估结果
     *
     * @param expression 表达式
     * @param variables  评估所需变量
     * @return 评估结果
     */
    public static boolean evaluateExpression(String expression, Map<String, Object> variables) {
        Expression compile = AviatorEvaluator.compile(expression.replace("$", ""));
        Object result = compile.execute(variables);
        return result instanceof Boolean ? (Boolean) result : false;
    }

    /**
     * 深度优先遍历(递归形式)
     *
     * @param from      开始节点
     * @param to        目标节点
     * @param variables 判断变量
     * @return 可到达路径
     */
    public static List<List<FlowElement>> findPathsRecursive(FlowNode from, FlowNode to, Map<String, Object> variables) {
        List<List<FlowElement>> result = new ArrayList<>();
        findPath(result, from, to, variables, Collections.singletonList(from));
        return result;
    }

    /**
     * 递归查找路径
     *
     * @param result    结果
     * @param curNode   节点
     * @param to        目标节点
     * @param variables 判断变量
     * @param curPath   路径
     */
    private static void findPath(List<List<FlowElement>> result, FlowNode curNode, FlowNode to, Map<String, Object> variables, List<FlowElement> curPath) {
        if (curNode.getId().equals(to.getId())) {
            result.add(curPath);
            return;
        }

        Set<SequenceFlow> outgoing = getOutgoing(curNode, variables);
        for (SequenceFlow sequenceFlow : outgoing) {
            FlowNode flowNode = (FlowNode) sequenceFlow.getTargetFlowElement();
            List<FlowElement> path = new ArrayList<>(curPath);
            path.add(sequenceFlow);
            path.add(flowNode);
            findPath(result, flowNode, to, variables, path);
        }
    }

    /**
     * 深度优先遍历(非递归)
     *
     * @param from      开始节点
     * @param to        目标节点
     * @param variables 判断变量
     * @return 可到达路径
     */
    /*public static List<List<FlowElement>> findPathsDFS(FlowNode from, FlowNode to, Map<String, Object> variables) {
        Stack<FlowNode> nodeStack = new Stack<>();
        nodeStack.push(from);

        Stack<List<FlowElement>> pathStack = new Stack<>();
        pathStack.push(Collections.singletonList(from));

        List<List<FlowElement>> result = new ArrayList<>();

        while (!nodeStack.isEmpty()) {
            FlowNode curNode = nodeStack.pop();
            List<FlowElement> curPath = pathStack.pop();

            if (curNode.getId().equals(to.getId())) {
                result.add(curPath);
            } else {
                Set<SequenceFlow> outgoing = getOutgoing(curNode, variables);
                for (SequenceFlow sequenceFlow : outgoing) {
                    FlowNode flowNode = (FlowNode) sequenceFlow.getTargetFlowElement();
                    nodeStack.push(flowNode);
                    List<FlowElement> path = new ArrayList<>(curPath);
                    path.add(sequenceFlow);
                    path.add(flowNode);
                    pathStack.push(path);
                }
            }
        }
        return result;
    }*/

    /**
     * 深度优先遍历(非递归)
     *
     * @param from      开始节点
     * @param to        目标节点
     * @param variables 判断变量
     * @return 可到达路径
     */
    public static List<List<FlowElement>> findPathsDFS(FlowNode from, FlowNode to, Map<String, Object> variables) {
        Stack<FlowNode> nodeStack = new Stack<>();
        nodeStack.push(from);

        Stack<List<FlowElement>> pathStack = new Stack<>();
        pathStack.push(Collections.singletonList(from));

        List<List<FlowElement>> result = new ArrayList<>();

        while (!nodeStack.isEmpty()) {
            FlowNode curNode = nodeStack.pop();
            List<FlowElement> curPath = pathStack.pop();

            if (curNode.getId().equals(to.getId())) {
                result.add(curPath);
            } else {
                Set<SequenceFlow> outgoing = getOutgoing(curNode, variables);
                for (SequenceFlow sequenceFlow : outgoing) {
                    FlowNode flowNode = (FlowNode) sequenceFlow.getTargetFlowElement();
                    if (curPath.stream().filter(flowElement -> flowElement.equals(flowNode)).count() < 2) { // 一条路径上同一个环节只能出现两次
                        nodeStack.push(flowNode);
                        List<FlowElement> path = new ArrayList<>(curPath);
                        path.add(sequenceFlow);
                        path.add(flowNode);
                        pathStack.push(path);
                    }
                }
            }
        }
        return result;
    }

    /**
     * 广度优先遍历(非递归)
     *
     * @param from      开始节点
     * @param to        目标节点
     * @param variables 判断变量
     * @return 可到达路径
     */
    public static List<List<FlowElement>> findPathsBFS(FlowNode from, FlowNode to, Map<String, Object> variables) {
        Queue<FlowNode> nodeQueue = new LinkedList<>();
        nodeQueue.add(from);

        Queue<List<FlowElement>> pathQueue = new LinkedList<>();
        pathQueue.add(new ArrayList<>());

        List<List<FlowElement>> result = new ArrayList<>();

        while (!nodeQueue.isEmpty()) {
            FlowNode curNode = nodeQueue.remove();
            List<FlowElement> curPath = pathQueue.remove();

            if (curNode.getId().equals(to.getId())) {
                curPath.add(curNode);
                result.add(curPath);
            } else {
                Set<SequenceFlow> outgoing = getOutgoing(curNode, variables);
                for (SequenceFlow sequenceFlow : outgoing) {
                    FlowNode flowNode = (FlowNode) sequenceFlow.getTargetFlowElement();
                    nodeQueue.add(flowNode);
                    List<FlowElement> path = new ArrayList<>(curPath);
                    path.add(curNode);
                    path.add(sequenceFlow);
                    pathQueue.add(path);
                }
            }
        }
        return result;
    }

    public static Task getTask(TaskService taskService, String taskId, String businessKey) {
        Task task = getTask(taskService, taskId);
        if (task != null) {
            return task;
        }
        if (StringUtils.hasText(businessKey)) {
            List<Task> tasks = taskService
                    .createTaskQuery()
                    .processInstanceBusinessKey(businessKey)
                    .taskCandidateOrAssigned(SecurityUtils.getUser().getUsername())
                    .list();
            return CollUtil.getFirst(tasks);
        }
        return null;
    }

    public static Task getTask(TaskService taskService, String taskId) {
        if (StringUtils.hasText(taskId)) {
            return taskService
                    .createTaskQuery()
                    .taskId(taskId)
                    .taskCandidateOrAssigned(SecurityUtils.getUser().getUsername())
                    .singleResult();
        }
        return null;
    }

    public static boolean isExpression(String expression) {
        return Pattern.compile("\\$\\{.*}").matcher(expression).find();
    }

    public static <T> Set<T> getExtension(UserTask userTask, String prop, Class<T> clazz) {
        List<ExtensionElement> elements = userTask.getExtensionElements().get(prop);
        if (elements != null && elements.size() > 0) {
            String assigneeUsersJson = elements.get(0).getElementText();
            List<T> list = JSON.parseArray(assigneeUsersJson, clazz);
            return new HashSet<>(list);
        }
        return new HashSet<>();
    }

    public static Set<Handler> getNodeHandlers(SysUserService sysUserService, ActorUserService actorUserService, String processInstanceId, UserTask userTask) {
        Set<Handler> assigneeUsers = getExtension(userTask, "assigneeUsers", Handler.class);
        Set<SysPost> assigneeGroups = getExtension(userTask, "assigneeGroups", SysPost.class);
        Set<ActorUser> assigneeRules = getExtension(userTask, "assigneeRules", ActorUser.class);

        if (CollUtil.isNotEmpty(assigneeGroups)) {
            List<SysUser> sysUsers = sysUserService.findByGroupCodeList(assigneeGroups.stream().map(sysPost -> sysPost.getPostId().toString()).collect(Collectors.toList()));
            if (sysUsers != null && sysUsers.size() > 0) {
                sysUsers.forEach(sysUser -> assigneeUsers.add(new Handler(sysUser)));
            }
        }
        if (CollUtil.isNotEmpty(assigneeRules)) {
            List<ActorInstance> actorInstances = actorUserService.getActorInstances(
                    assigneeRules.stream().map(rule -> rule.getId().toString()).collect(Collectors.toList()),
                    processInstanceId);
            if (actorInstances != null && actorInstances.size() > 0) {
                actorInstances.forEach(actorInstance -> assigneeUsers.add(new Handler(actorInstance)));
            }
        }
        return assigneeUsers;
    }

    public static List<ExtensionElement> getExtensionElements(FlowElement flowElement, String customPropertyName) {
        if (flowElement instanceof UserTask) {
            UserTask userTask = (UserTask) flowElement;
            Map<String, List<ExtensionElement>> extensionElements = userTask.getExtensionElements();
            if (MapUtil.isNotEmpty(extensionElements)) {
                List<ExtensionElement> values = extensionElements.get(customPropertyName);
                if (CollUtil.isNotEmpty(values)) {
                    return values;
                }
            }
        }
        return null;
    }

    public static String getSingleCustomPropertyValue(List<ExtensionElement> customProperty) {
        String value = null;
        if (CollUtil.isNotEmpty(customProperty)) {
            ExtensionElement extensionElement = customProperty.get(0);
            if (extensionElement != null) {
                value = extensionElement.getElementText();
            }
        }
        return value;
    }

    public static String getSingleCustomProperty(FlowElement flowElement, String customPropertyName) {
        List<ExtensionElement> extensionElements = getExtensionElements(flowElement, customPropertyName);
        return getSingleCustomPropertyValue(extensionElements);
    }

    public static String getStrValue(ProcessEngineConfigurationImpl processEngineConfiguration, RuntimeService runtimeService, TypeConverter typeConverter, String processInstanceId, String exp) {
        return getValue(processEngineConfiguration, runtimeService, typeConverter, processInstanceId, exp, String.class);
    }

    public static <T> T getValue(ProcessEngineConfigurationImpl processEngineConfiguration, RuntimeService runtimeService, TypeConverter typeConverter, String processInstanceId, String exp, Class<T> clazz) {
        Object value = getValue(processEngineConfiguration,
                runtimeService, processInstanceId, exp);
        return typeConverter.convert(value, clazz);
    }

    public static Object getValue(ProcessEngineConfigurationImpl processEngineConfiguration, RuntimeService runtimeService, String processInstanceId, String exp) {
        org.flowable.common.engine.api.delegate.Expression expression = processEngineConfiguration.getExpressionManager().createExpression(exp);
        ExecutionEntity executionEntity = (ExecutionEntity) runtimeService
                .createProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .includeProcessVariables()
                .singleResult();
        return expression.getValue(executionEntity);
    }

    public static <T, K> Map<K, T> toMap(Collection<T> entities, Function<T, K> transfer) {
        Map<K, T> map = new HashMap<>();
        entities.forEach(t -> map.put(transfer.apply(t), t));
        return map;
    }

    public static List<HistoricTaskInstance> getNodeDoneHistoricTaskInstancesByProcessInstanceId(HistoryService historyService, String processInstanceId, Collection<String> activityIds) {
        return historyService
                .createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId)
                .includeIdentityLinks()
                .taskDefinitionKeys(activityIds)
                .orderByTaskCreateTime()
                .desc()
                .list();
    }

    public static List<HistoricTaskInstance> getAllHistoricTaskInstances(HistoryService historyService, String businessKey) {
        return historyService
                .createHistoricTaskInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .orderByTaskCreateTime()
                .desc()
                .list();
    }

    public static List<HistoricActivityInstance> getHistoricSequenceFlowActivityInstances(HistoryService historyService, String processInstanceId, String type) {
        return historyService
                .createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .activityType(type)
                .list();
    }

    public static List<HistoricActivityInstance> getAllHistoricActivityInstances(HistoryService historyService, String processInstanceId) {
        return historyService
                .createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .list();
    }
}

